﻿//#define DEVELOPMENT
#undef DEVELOPMENT
//#define NOLOGGING
#undef NOLOGGING

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Threading;
using InstanceController.RolesPerfMonDAL;
using InstanceController.StateMachineDAL;
using InstanceControllerWorker.DynaScale;
using InstanceControllerWorker.ManageCommands;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using WADConfigureWebRole;

namespace InstanceControllerWorker
{
	public class WorkerRole : RoleEntryPoint
	{
		/// <summary>
		/// Processor % utilization needs to be smoothed over some time interval to give good results.
		/// </summary>

		public const string ActionIncrement = "Increment";
		public const string ActionDecrement = "Decrement";
		public const string ActionManual = "Manual";

		#region public global static fields
		public static InstanceController.ConfigurationDAL.ConfigurationDataModel ConfigurationDataModel;
		public static TimeDefinitions TimeDefinitions = new TimeDefinitions();		//This class has default settings for the time intervals.
		#endregion

		#region private field initializations
		private static bool _configurationSuccess = false;
		private static ApplicationSettings _appSettings = new ApplicationSettings();
		#endregion

		#region OnStart method and role initialization
		public override bool OnStart()
		{
			// Set the maximum number of concurrent connections 
			ServicePointManager.DefaultConnectionLimit = 12;

			// For information on handling configuration changes
			// see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.

			//Initialize your Table Storage tables and connections (if any) using the values in the xml file
			//some settins may be overridden by the configuration stored in table storage.
			_appSettings = ApplicationSettingsManager.DynamicObjectMethods.GetServiceSourcePropertyValues(
				new ApplicationSettings(),
				new ApplicationSettingsManager.Manager().AppSettings);				// loads the static ApplicationSettings object from the xml file.

			ConfigureTableStorageAccess();											// Required for cloud storage.
			SetupApplicationTableStorage();											// Define your table storage connections 
			InitializeConfigurationDataModelObject();								// Set the configuration table partition and row
			InitializeInstanceController();											// Sets the Instance Controller tables in your application's table storage space

			return base.OnStart();
		}

		#region Initialization and Configuration section

		/// <summary>
		/// Setup CloudStorageAccount Configuration Setting Publisher - 
		/// This code sets up a handler to update CloudStorageAccount instances when their corresponding
		/// configuration settings change in the service configuration file.
		/// </summary>
		private static void ConfigureTableStorageAccess()
		{
			// This code sets up a handler to update CloudStorageAccount instances when their corresponding
			// configuration settings change in the service configuration file.
			CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
			{
				// Provide the configSetter with the initial value
				configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
				RoleEnvironment.Changed += (sender1, arg) =>
				{
					if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>().Any(change =>
						(change.ConfigurationSettingName == configName)))
					{
						// The corresponding configuration setting has changed, propagate the value
						if (
							!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
						{
							// In this case, the change to the storage account credentials in the
							// service configuration is significant enough that the role needs to be
							// recycled in order to use the latest settings. (for example, the 
							// endpoint has changed)
							RoleEnvironment.RequestRecycle();
						}
					}
				};

			});
		}

		/// <summary>
		/// Setups the application table storage.
		/// </summary>
		private static void SetupApplicationTableStorage()
		{
			// For production code see discussion in AzureTableInit.cs
			// If you hard-code the suggested properties then the following code can be safely removed.
			// Optionally you could hard-code the table names and leave DataConnectionString more
			// dynamic in case you change your Azure account settings for any reason. In that case
			// you would leave all the following code, the table name will simply be replaced with the
			// hard-code version.

			//Initialize settings in your Azure Table Storage objects. You should have initialization for your
			//DataConnectionString and for each table name that you have created.
			//YOURNAMESPACE.CLASSNAME.AzureTableInitialization.SetAzureTableContextDefaults(
			//    Properties.Settings.Default.AzureDataConnectionString, Properties.Settings.Default.YOURDEFINITIONTableName);


			//This creates tables if needed.
			//if (
			//    !YOURNAMESPACE.CLASSNAME.AzureTableInitialization.InitializeTables(
			//        Properties.Settings.Default.AzureDataConnectionString, Properties.Settings.Default.YOURDEFINITIONTableName))
			//    throw new StorageClientException();
			// If you get an error here there is a problem with your DataConnectionString or with your
			// table name. Check that they exist and that there are not invalid characters in the names.
			// More detail can be gotten by setting breakpoints in the called method.

			// Additional table initializations here.
		}

		/// <summary>
		/// Initializes the configuration data model object.
		/// This is duplicated in the workerrole.cs
		/// </summary>
		public static void InitializeConfigurationDataModelObject()
		{
			ConfigurationDataModel = new InstanceController.ConfigurationDAL.ConfigurationDataModel(
				_appSettings.ConfigurationPartitionKey, _appSettings.ConfigurationRoleRowKey);
		}

		/// <summary>
		/// Initializes the instance controller. Hooks up or creates the diagnositics and management
		/// table storage.
		/// </summary>
		private static void InitializeInstanceController()
		{
			#region Start Table Initialization Section

			// *********************************************************************************************************
			// **** Table Storage Exceptions are almost always related to missing configuration connection strings. ****
			// **** This can be confusing.  In our configuration Application Settings there are several connection  ****
			// **** string definitions that all have the same value "TableStorageConnectionString". You only need   ****
			// **** a single entry in your WebRole properties that takes the value as the key and its value as the  ****
			// **** actual Azure connection. The reason for this is that it allows flexibility in the future for    ****
			// **** should the need arise to split the service across multiple storage accounts.                    ****
			// *********************************************************************************************************

			// For production code see discussion in AzureTableInit.cs
			// If you hard-code the suggested properties then the following code can be safely removed.
			// Optionally you could hard-code the table names and leave DataConnectionString more
			// dynamic in case you change your Azure account settings for any reason. In that case
			// you would leave all the following code, the table name will simply be replaced with the
			// hard-code version.

			//Initialize settings in your Azure Table Storage objects. You should have initialization for your
			//DataConnectionString and for each table name that you have created.
			InstanceController.ConfigurationDAL.AzureTableInitialization.SetAzureTableContextDefaults(
				_appSettings.AzureDataConnectionString, _appSettings.ConfigurationDALTableName);


			//This creates tables if needed.
			if (
				!InstanceController.ConfigurationDAL.AzureTableInitialization.InitializeTables(
					_appSettings.AzureDataConnectionString, _appSettings.ConfigurationDALTableName))
				throw new StorageClientException();
			// If you get an error here there is a problem with your DataConnectionString or with your
			// table name. Check that they exist in the workerrole properties and that there are not invalid
			// characters in the names. More detail can be gotten by setting breakpoints in the called method.

			// Additional table initializations here.
			InstanceController.StateMachineDAL.AzureTableInitialization.SetAzureTableContextDefaults(
				_appSettings.StateMachineConnectionString, _appSettings.StateMachineTableName);
			if (
				!InstanceController.StateMachineDAL.AzureTableInitialization.InitializeTables(
					_appSettings.StateMachineConnectionString, _appSettings.StateMachineTableName))
				throw new StorageClientException();

			InstanceController.RolesPerfMonDAL.AzureTableInitialization.SetAzureTableContextDefaults(
				_appSettings.PerformanceCountersConnectionString, _appSettings.CounterSettingsTableName);
			if (!InstanceController.RolesPerfMonDAL.AzureTableInitialization.InitializeTables(_appSettings.PerformanceCountersConnectionString, _appSettings.CounterSettingsTableName))
				throw new StorageClientException();

			InstanceController.WADPerformanceCountersDAL.AzureTableInitialization.SetAzureTableContextDefaults(
				_appSettings.WADiagnosticConnectionString, _appSettings.WADPerformanceCountersDALTableName);
			if (!InstanceController.WADPerformanceCountersDAL.AzureTableInitialization.InitializeTables(_appSettings.WADiagnosticConnectionString, _appSettings.WADPerformanceCountersDALTableName))
				throw new StorageClientException();


			#endregion End  Table Initialization Section
		}

		#endregion
		#endregion

		public override void Run()
		{
			Trace.WriteLine("WorkerRole entry point called", "Information");
			LogProcess("Run", "Starting -  WorkerRole entry point called");

			DoCheckServerCertificate(false);								// Set up a call back event handler for certificates.
			_configurationSuccess = GetConfigurationSettings();				// Get settings on initial run if they exist.
			var evaluation = new Evaluation(_configurationSuccess);			// Run an initial evaluation if there is a configuration.
			CSManageCommand.CommandWasAborted = false;						// reset this temp variable to a known value

			LogProcess("Run", "Ending -  Configuration " + (_configurationSuccess ? "true" : "false"));

			LogProcess("Run", "Entering main loop.");
			while (true)													// Enter the main worker loop
			{
				LogProcess("MainLoop", "Begin");
				if (_configurationSuccess)									// Are we configured to run the Instance Controller?
				{
					LogProcess("MainLoop", "Configured:true Execute:GetConfigurationSetting");
					if (!GetConfigurationSettings()) // Check if configuration still active.
					{
						_configurationSuccess = false; // Could have had the configuration deleted or it still is not available
						evaluation.CanRun = false;
						// thread will sleep, nothing to do right now...
						LogProcess("MainLoop", "Configured:false status changed to InActive or has been deleted");
					}
					else
					{
						LogProcess("MainLoop", "Configuration:true:updated Execute:SetCSManagerCommandProperties");
						SetCSManagerCommandProperties(); // Do this first in the evaluation process
						LogProcess("MainLoop", "Execute:DoGetDeploymentProcess");
						DoGetDeploymentProcess(); // Get current deployment status (updates vars in CSManagerComand)
						if (ConfigurationDataModel.ManualMode) // Are we in manual mode?
						{
							LogProcess("MainLoop", "ManualMode");
							if (CSManageCommand.CurrentInstanceCount != ConfigurationDataModel.ManualInstances)// Do we need to change the instances
							{
								var metric = new Metric
								             	{
								             		CurrentValue = 0,
								             		Name = "Manual Mode",
								             		Timestamp = DateTime.Now,
								             		Tag = "ManualInstances"
								             	};
								LogProcess("MainLoop", "Execute:RunProcess");
								RunProcess(); // yes, go make changes
								LogProcess(metric, ActionManual);
								// thread will sleep on return...
							}
						}
						else
						{
							try
							{
								LogProcess("MainLoop", "AutoMode");
								// Wakeup and evaluate current system status.
								var metric = new Metric();
								LogProcess("MainLoop", "Execute:evaluation.EvaluateSystemStatus");
								int triStateResult = evaluation.EvaluateSystemStatus(out metric);
								switch (triStateResult)
								{
									case -1: //at or below decrement threshold
										CSManageCommand.IsIncrement = false;
										LogProcess("MainLoop", "Execute:RunProcess:Decrement");
										RunProcess(); //go make changes as required
										if (!CSManageCommand.CommandWasAborted)
											LogProcess(metric, ActionDecrement);
										// thread will sleep on return...
										break;
									case 0: //no-action
#if (DEVELOPMENT)
									Trace.WriteLine(String.Format("Simulated No Changes: {0}", CSManageCommand.CurrentInstanceCount),
									                "Information");
#endif
										LogProcess("MainLoop", "No Action");
										// thread will sleep, nothing to do right now...
										break;
									case 1: //at or above increment threshold
										CSManageCommand.IsIncrement = true;
										LogProcess("MainLoop", "Execute:RunProcess:Increment");
										RunProcess(); //go make changes as required
										if (!CSManageCommand.CommandWasAborted)
											LogProcess(metric, ActionIncrement);
										// thread will sleep on return...
										break;
								}
							}
							catch (Exception ex)
							{
								LogProcess("MainLoop:Evaluation", "Failed during evaluation: " + ex.Message);
							}
						}
					}
				}
				else
				{
					try
					{
						LogProcess("MainLoop", "Configured:false Execute:GetConfigurationSetting");
						_configurationSuccess = GetConfigurationSettings();
							// Has the system been updated and now configured for Instance Controller?
						if (_configurationSuccess) // If so then configure the Evlauation
						{
							LogProcess("MainLoop", "Configured:true Execute:evaluation.ConfigureEvaluationInstance");
							evaluation.ConfigureEvaluationInstance(); // This is run now that the configuration has become successful.
						}
					}
					catch (Exception ex)
					{
						LogProcess("MainLoop:EvaluationConfiguration", "Failed during evaluation configuration: " + ex.Message);
					}
				}
				Trace.WriteLine(String.Format("Configuration: {0}", _configurationSuccess), "Information");
				LogProcess("MainLoop", "End");

				int sleepTime = 30000;
				if (ConfigurationDataModel != null)
					sleepTime = ConfigurationDataModel.MainLoopSleepTime * 1000;	//sleep time in milliseconds
				
				// Clean up temp variables
				CSManageCommand.CommandWasAborted = false;
				LogProcess("MainLoop", "Sleep " + sleepTime);

				Thread.Sleep(sleepTime);
				Trace.WriteLine("Working", "Information");
				LogProcess("MainLoop", "Wakeup");
			}
		}

		/// <summary>
		/// Runs the changedeployment process.
		/// </summary>
		private static void RunProcess()
		{
#if (DEVELOPMENT)
			// Simulate instancing changes. The simulated command uses identical decision code for the evaluation.
			SimulateGetDeployment();
			if (CheckInstancingRules() == 0)		// no change
			{
				Trace.WriteLine(String.Format("Simulated Rules No Changes: {0}", CSManageCommand.CurrentInstanceCount), "Information");
				CSManageCommand.CommandWasAborted = true;
				return;
			}

			if (ConfigurationDataModel.ManualMode)
				Trace.WriteLine(String.Format("Simulated Manual Mode change: {0}", CSManageCommand.CurrentInstanceCount),
								"Information");
			else
			{
				if (CSManageCommand.IsIncrement)
					Trace.WriteLine(String.Format("Simulated Auto Mode add: {0}", CSManageCommand.CurrentInstanceCount),
					                "Information");
				else
					Trace.WriteLine(String.Format("Simulated Auto Mode subtract: {0}", CSManageCommand.CurrentInstanceCount),
									"Information");
			}
#else
			if (CheckInstancingRules() == 0)						// need to make a configuration change?
			{
				LogProcess("PerformOperation", "Command Was Aborted: no changes");
				CSManageCommand.CommandWasAborted = true;
				return; // no change
			}

			LogProcess("RunProcess", "Execute:GetDeploymentCommand");
			CSManageCommand.ConfigXML = null;						// clear any old configurations
			(new GetDeploymentCommand()).Run();						// read it the first time to pick up the current configuration

			// Checking for null here so that we won't even begin a call to modify the configuration. Doing this avoids an
			// error generated if you start the process and have to abort. Reason is the creation of a service context that is 
			// partially created.  It would take a lot of code to tear it back down that is unnecessary.
			if (CSManageCommand.ConfigXML != null)					// when a configuration is in then make the change
			{
				LogProcess("RunProcess", "Execute:ChangeDeploymentConfigCommand");
				(new ChangeDeploymentConfigCommand()).Run();
			}
#endif
		}

		/// <summary>
		/// Logs the process. The partitionKey is a concantnation of: subscriptionID  + "__" + actionName
		/// The rowkey first part is time ticks at the point the action completed + a Guid to guarantee unique-ness.
		/// The CommandTimeStamp is the time ticks when the action was initiated.  Thus the difference is how long 
		/// it took Azure to complete the action.
		/// </summary>
		/// <param name="metric">The metric.</param>
		/// <param name="actionName">Name of the action.</param>
		public static void LogProcess(Metric metric, string actionName)
		{
			var partitionKey = ConfigurationDataModel.SubscriptionID + "__" + actionName;

			// This time stamp is the current time after the action completes (success or fail)
			DateTime dt = DateTime.Now;
			var dt1 = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Millisecond);
			var dt2 = new DateTime(metric.Timestamp.Year, metric.Timestamp.Month, metric.Timestamp.Day, metric.Timestamp.Hour, metric.Timestamp.Minute, metric.Timestamp.Second, metric.Timestamp.Millisecond);
			
			var rowKey = dt1.ToUniversalTime().Ticks + "__" + Guid.NewGuid().ToString("N");
			var stateDataModel = new StateDataModel(partitionKey, rowKey)
			{
				CommandTimeStamp = dt2.ToUniversalTime().Ticks.ToString(),	// This is time that the action was initiated
				CounterName = metric.Name,
				CurrentMetricValue = metric.CurrentValue.ToString(),
				ProcessName =
					String.Format("ChangeConfiguration: {0} count: {1}, results: {2}", actionName,
								  CSManageCommand.DecrementStepValue, CSManageCommand.CurrentInstanceCount),
				Tag = metric.Tag,
				Action = actionName,
				ActionResult = CSManageCommand.CurrentInstanceCount.ToString(),
				DeploymentSlot = ConfigurationDataModel.Slot,
				RoleName = ConfigurationDataModel.RoleName
			};
			StateDataMethods.SaveState(stateDataModel);
		}

		/// <summary>
		/// Logs the process.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <param name="message">The message.</param>
		public static void LogProcess(string name, string message)
		{
#if (NOLOGGING)
			return;
#endif
			var partitionKey = ConfigurationDataModel.SubscriptionID + "__DataLog";

			// This time stamp is the current time after the action completes (success or fail)
			DateTime dt = DateTime.Now;
			var dt1 = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Millisecond);

			var rowKey = dt1.ToUniversalTime().Ticks + "__" + Guid.NewGuid().ToString("N");
			var stateDataModel = new StateDataModel(partitionKey, rowKey)
			{
				CommandTimeStamp = dt1.ToUniversalTime().Ticks.ToString(),	// This is time that the action was initiated
				CounterName = String.Empty,
				CurrentMetricValue = String.Empty,
				ProcessName = String.Empty,
				Action = name,
				ActionResult = message,
				DeploymentSlot = ConfigurationDataModel.Slot,
				RoleName = ConfigurationDataModel.RoleName
			};
			StateDataMethods.SaveState(stateDataModel);

		}

		/// <summary>
		/// Does a getdeployment command process.
		/// </summary>
		private static void DoGetDeploymentProcess()
		{
#if (DEVELOPMENT)
			// Simulate getting instance count end from deployment
			CSManageCommand.CurrentInstanceCount = 2;
#else
			(new GetDeploymentCommand()).Run();
			// maintain an up to date configuration since people could make changes through the portal  
#endif
		}

		/// <summary>
		/// Sets the CS manager command properties.
		/// Pick up any configuration changes that might have occured.
		/// </summary>
		private static void SetCSManagerCommandProperties()
		{
			CSManageCommand.SubscriptionId = ConfigurationDataModel.SubscriptionID;
			CSManageCommand.HostedServiceName = ConfigurationDataModel.HostServiceName;
			CSManageCommand.RoleName = ConfigurationDataModel.RoleName;
			CSManageCommand.CertificateThumbprint = ConfigurationDataModel.CertificateThumbPrint;
			CSManageCommand.DeploymentSlot = ConfigurationDataModel.Slot;
			CSManageCommand.DeploymentId = ConfigurationDataModel.DeploymentID;
			CSManageCommand.MaxInstances = ConfigurationDataModel.MaxInstances;
			CSManageCommand.MinInstances = ConfigurationDataModel.MinInstances;
			CSManageCommand.IncrementStepValue = ConfigurationDataModel.IncrementStepValue;
			CSManageCommand.DecrementStepValue = ConfigurationDataModel.DecrementStepValue;
			CSManageCommand.ManualMode = ConfigurationDataModel.ManualMode;
			CSManageCommand.ManualInstances = ConfigurationDataModel.ManualInstances;
			CSManageCommand.CurrentInstanceCount = 1;				// This is picked up by the GetDeploymentCommand()  
			CSManageCommand.IsIncrement = true;						// evaluate will set this as needed
			CSManageCommand.TreatWarningsAsError = false;			// debugging tool for tracing
#if (!DEVELOPMENT)
			CSManageCommand.ValidateCertificate();					// check our cert against the server's version and caches it in the CSManagerCommand
#endif
		}

		/// <summary>
		/// Gets the configuration settings from table storage into the static global ConfigurationDataModel. 
		/// If there is no configuration then the system returns false subsequently should block 
		/// Instance Controller from trying to control system. When the controlled webrole first runs it sets
		/// a part of the configuration with the IsActive set to false. When the configuration tool is run it
		/// set the IsActive to true.
		/// </summary>
		/// <returns>false if configuration is null</returns>
		private static bool GetConfigurationSettings()
		{
			// Get the Processor% Utilization time interval settings
			var counterSettingsDataModelRow = CounterSettingsDataMethods.Read(ConfigurationDataModel.SubscriptionID,
			                                                                  PlugIn.ProcessorFriendlyName);
			if(counterSettingsDataModelRow != null)
			{
				TimeDefinitions.FastAttack = counterSettingsDataModelRow.FastAttack;
				TimeDefinitions.LongInterval = counterSettingsDataModelRow.LongInterval;
				TimeDefinitions.ShortInterval = counterSettingsDataModelRow.ShortInterval;
				TimeDefinitions.SpinUpDelay = counterSettingsDataModelRow.SpinUpDelay;
			}

			
			// If the read finds nothing it returns null.
			ConfigurationDataModel =
				InstanceController.ConfigurationDAL.ConfigurationDataMethods.ReadRow(ConfigurationDataModel.PartitionKey,
				                                                                     ConfigurationDataModel.RowKey);
			// If the ConfigurationDataModel is null create it
			if (ConfigurationDataModel != null)
				return ConfigurationDataModel.IsActive;		// The configuration may not be completely initialized.

			InitializeConfigurationDataModelObject();		// initialize if it was null
			return false;
		}

		/// <summary>
		/// Gets the performance counters list from table storage.
		/// </summary>
		/// <returns></returns>
		public static List<PerformanceCounterItem> GetPerformanceCountersList()
		{
			var performanceCounterItems = new List<PerformanceCounterItem>();
			var counterSettingsDataModelRows =
				InstanceController.RolesPerfMonDAL.CounterSettingsDataMethods.ReadCounterSettingsDataModelRows(
					ConfigurationDataModel.SubscriptionID);
			foreach (var counterSettingsDataModelRow in counterSettingsDataModelRows)
			{
				var pci = new PerformanceCounterItem
				{
					CounterName = counterSettingsDataModelRow.CounterName,
					DecThreshold = counterSettingsDataModelRow.DecThreshold,
					IncThreshold = counterSettingsDataModelRow.IncThreshold,
					PaddingSeconds = counterSettingsDataModelRow.PaddingSeconds,
					PollingInterval = counterSettingsDataModelRow.PollingInterval,
					PriorityLevel = counterSettingsDataModelRow.PriorityLevel
				};
				if (pci.PollingInterval >= 1)
					performanceCounterItems.Add(pci);
			}
			return performanceCounterItems;
		}

		/// <summary>
		/// Does the check server certificate validation. If this computer does not have the proper certificate 
		/// installed in the certificate store it will be rejected.
		/// </summary>
		/// <param name="ignoreSslPolicyErrors"></param>
		private static void DoCheckServerCertificate(bool ignoreSslPolicyErrors)
		{
			LogProcess("DoCheckServerCertificate", "Checking"); 
			ServicePointManager.ServerCertificateValidationCallback =
				((sender, certificate, chain, sslPolicyErrors) =>
				 	{
						if (ignoreSslPolicyErrors)
							return true;
				 		if (sslPolicyErrors == SslPolicyErrors.None)
				 			return true;

				 		Trace.WriteLine(String.Format("  Certificate error: {0}", sslPolicyErrors));
						LogProcess("DoCheckServerCertificate", String.Format("  Certificate error: {0}", sslPolicyErrors)); 

				 		// Do not allow this client to communicate with unauthenticated servers.
				 		return false;
				 	}
				);
		}

		/// <summary>
		/// Checks the instancing rules. Sets the CommandToInstances to the desired
		/// count to be configured. (with 0 = no change).
		/// </summary>
		/// <returns></returns>
		public static int CheckInstancingRules()
		{
			int instanceCount = 0;
			if (CSManageCommand.ManualMode)
			{
				if (CSManageCommand.ManualInstances <= CSManageCommand.MaxInstances && CSManageCommand.ManualInstances > 0)
				{
					if (CSManageCommand.ManualInstances != CSManageCommand.CurrentInstanceCount)
						instanceCount = CSManageCommand.ManualInstances;
				}
			}
			else
			{
				// Incrementing / Decrementing? 
				if (CSManageCommand.IsIncrement)
				{
					if (CSManageCommand.CurrentInstanceCount < CSManageCommand.MaxInstances)
					{
						if (CSManageCommand.CurrentInstanceCount + CSManageCommand.IncrementStepValue <= CSManageCommand.MaxInstances)
							instanceCount = CSManageCommand.CurrentInstanceCount + CSManageCommand.IncrementStepValue;
						else
							instanceCount = CSManageCommand.MaxInstances;
					}
				}
				else
				{
					// Do not go below 1 instance.
					if (CSManageCommand.CurrentInstanceCount != 1 && CSManageCommand.CurrentInstanceCount > CSManageCommand.MinInstances)
					{
						if (CSManageCommand.CurrentInstanceCount - CSManageCommand.DecrementStepValue <= CSManageCommand.MinInstances)
							instanceCount = CSManageCommand.MinInstances;
						else
							instanceCount = CSManageCommand.CurrentInstanceCount - CSManageCommand.DecrementStepValue;

					}
					else
					{
						if (CSManageCommand.CurrentInstanceCount < CSManageCommand.MinInstances)
						{
							//if the demand is low but someone has raised the minimun instances above the current count
							//this will raise the count up to the minimum
							instanceCount = CSManageCommand.MinInstances;
							CSManageCommand.IsIncrement = true;
						}
					}
				}
			}
			LogProcess("RunProcess", "CheckInstancingRules: Set instance count to: " + instanceCount);
			CSManageCommand.CommandToInstances = instanceCount;
			return instanceCount;
		}

		/// <summary>
		/// Checks for active changes pending. True if a change is pending.
		/// </summary>
		/// <returns></returns>
		public static bool CheckForActiveChangesPending()
		{
			return GetTimeSinceLastChangeConfiguration() <= TimeDefinitions.SpinUpDelay;
		}

		/// <summary>
		/// Gets the time since last change configuration command completed.
		/// </summary>
		/// <returns></returns>
		private static double GetTimeSinceLastChangeConfiguration()
		{
			var lastIncTick = Int64.Parse(StateDataMethods.GetLastActionTimeTick(ConfigurationDataModel.SubscriptionID, ActionIncrement));
			var lastDecTick = Int64.Parse(StateDataMethods.GetLastActionTimeTick(ConfigurationDataModel.SubscriptionID, ActionDecrement));
			var lastTick = lastIncTick - lastDecTick < 0 ? lastDecTick : lastIncTick;

			DateTime dt = DateTime.Now;
			var dt1 = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Millisecond);
			var t1 = dt1.ToUniversalTime().Ticks;

			TimeSpan ts = TimeSpan.FromTicks(t1 - lastTick);
			return ts.TotalMinutes;
		}


#if (DEVELOPMENT)
		private static void SimulateGetDeployment()
		{
			CSManageCommand.CurrentInstanceCount = 2;	//simulated return from the GetDeployment command.
		}
#endif

	}
}

/*
 * Configuration checked list:
 * 
 * Using instructions below create a "permanant" management certificate. It has a unique thumbprint and its purpose is management control.
 * Upload the .cer version to Azure Management Certificate store.
 * Upload the .pfx version to the Azure application certificates store.
 * If you are also using Remote Desktop Connection you will need an additional application for that purpose certificate (see instructions).
 * 
 * 
 * 
 */

/*
   Creating an X.509 Management Certificate
   Microsoft description: http://msdn.microsoft.com/en-us/library/gg432987(v=MSDN.10).aspx
   
   N O T E:
   The Microsoft description gives multiple methods to create your management certificate.
   Only *ONE* of these will work for more that one computer! That is the method below using the 
   command line tool. Using IIS or Visual Studio do not include the private key and are suitable only
   for development.
   
   Replace the CN= part after the (=) with a descriptive name.
   Replace the filename.cer with your full path export filename the ends with .cer (required by Azure).
   makecert -sky exchange -r -n "CN=FLHouseMgrCert" -pe -a sha1 -len 2048 -ss My "c:\Azure\CertStore\testDiagMgrCert.cer"
 
   How to Create and use Certificates
   1- Execute the above makecert command
   2- The file will be stored in your designated folder.
   3- Run Windows Certificate Manager (certmgr.msc)
   4- Open Personal/Certificates folder you should see the certificate there in the list.
   5- Right-click on the Certificate name and mouseover "All Tasks/Export..." This will launch the Certificate Export Wizard .
   6- Click Next.
   7- Click radio-button "Yes, export the private key"
   8- Click Next.
   9- Verify that radio-button Personal information Exchange is selected (should be by default.)
  10- Check these two Check-boxes ONLY: (un-check any others)
 		Include all certificates in the certification path, if possible
 		Export all extended properties
		Do *not* select "Delete the private key if export is successful"; this option disables your current server certificate
  11- Click Next.
  12- Enter and confirm a password
  13- Click Next.
  14- Browse to a location to store the certificate and give it a name that ends with ".pfx" (the default.)
  15- Click Next.
  16- Verify the content to export and click Next.
  17- Click Finish.
   
   You now have two certificates one that ends in .cer and one that ends with .pfx
   Using the Windows Azure Portal upload the management certificate (ends with .cer) to the Management Certificates
   store on the portal.
 
   NOTE: If you will be changing your configuration from within Azure (a worker role for example) you must upload the
   pfx certificate version of the certificate to your applications certificates.  In short you must upload the same 
   certificate to both places.
   
   Your machine is effectively the Certificate Authority (CA) for that certificate. If you delete the certificate
   from your machine in the certificate store you will *not* be able to export it again even if you subsequently reload
   it from the .pfx file.
   If you want others to be able to also manage the Azure account you may distribute the .pfx certificate. 
   
   Here is an example.
   Suppose we have a three member team contracted that will be working on an Azure Project. We want all memebers to be able to use
   the management services API. We also want to be able to remove the contractor access once the project completes without disrupting
   the running services. We also want a staff team to be able to maintain the code after completion or we might want to hire
   other contractors from time to time.
   
   Azure allows up to five management certificates to be installed.
   
   To meet the requirement that the service will have perpetual management certificate we need a certificate that is issued by either
   a public Certificating Authority (CA) or our organization must have or create an in-house CA.
   
   During development team members can be issued .pfx certificates and its matching .cer certificate can be uploaded to Azure. Once 
   development completes the Development certificate can be deleted from the Azure Portal.
   
   Finally for manintenance an new development certificate can be generated and uploaded to Azure.  The matching .pfx can be installed
   on in-house machines.  Likewise contractors might be given a different certificate.
   
   From the discussion above we will need at least three certificates over the life of the project.
  
   The Perpetual Certificate
   Following the steps listed above we would create a perpetual certificate that will live indefinitely in our CA store.  In this case 
   we will name the certificate as shown below:
   makecert -sky exchange -r -n "CN=FLHousePermMgrCert" -pe -a sha1 -len 2048 -ss My "c:\Azure\CertStore\FLHousePermMgrCert.cer"
   We also will name the exported certificate likewise: FLHousePermMgrCert.pfx
   The permanent certificate is only use with internal application code.
   
   The Contractor Certificate
   Again following the steps listed above however any computer may be used to create the certificate since it will not be retained.
   makecert -sky exchange -r -n "CN=ContractorMgrCert" -pe -a sha1 -len 2048 -ss My "c:\Azure\CertStore\ContractorMgrCert.cer"
   We also will name the exported certificate likewise: ContractorMgrCert.pfx
   This certificate is provided to developer team members.  Care should be exercised in regularly replacing the certificates even
   during development or when a team member leaves.
  
   The Maintenance Certificate
   Again following the steps listed above however any computer may be used to create the certificate since it will not be retained.
   makecert -sky exchange -r -n "CN=MaintMgrCert" -pe -a sha1 -len 2048 -ss My "c:\Azure\CertStore\MaintMgrCert.cer"
   We also will name the exported certificate likewise: MaintMgrCert.pfx
   This certificate is provided to in-house developer team members.  Care should be exercised in regularly replacing the certificates.

 */

/*
	Initialization Issues with Diagnostics and Instance Scaling
 * 
 * Why are Performance Counters not initialized here?
 * Performance Counters not initialized here because the owner must enter configuration from his/her Azure 
 * Portal for the system to be able to manage instances. That information is not available here.
 * 
 * The discussion below is from the web role (monitored) instance point of view.
 * 
 * There are two parts to Diagnostics and Scaling
 * 1 - Azure Collection of Data Points
 * 2 - Worker Role processing of collected data points
 * 
 * 1-
 * Azure performs the actual collection of performance and event data.  The developer is tasked with
 * deciding which data points are "of interest" and configuring the collection of those int table storage.
 * Azure creates and maintains the tables to collect the data points.
 * 
 * Initialization of diagnostics should occur normally when the application starts.  However it is now
 * possible to re-configure diagnostics at anytime.  In this application it is done here in the OnStart().
 * 
 * 2-
 * The worker role relies on Azure and the web role(s) to create the data points of interest.  Since the 
 * web roles may vary dramatically and contain information that is specific to the web role it must create
 * its configuration that the worker role (Instance Monitor) will consume.
 * 
 * Initialization can be performed in a number of ways. In this case a web page has the inputs for users to
 * enter information obtained from the Azure Portal and information contained in the web.config files that
 * are decided by the development team such as web role names and sampling configuration settings.
 * 
 * These are entered either directly into the application settings or into the Global.ascx.cs performance
 * counters class.
 * 
 * The application cannot scale without these settings.
 * 
*/